feat(rest): support rest catalog for databases, tables and snapshots - #165
Merged
Conversation
SteNicholas
force-pushed
the
PAIMON-164
branch
2 times, most recently
from
August 3, 2026 05:31
54137c1 to
b86bf27
Compare
Member
Author
|
Cherry pick alibaba/paimon-cpp#441. |
Add a REST catalog selected via the "metastore=rest" option:
- RestHttpClient: blocking libcurl client with exponential-backoff
retries. 429/503 are retried for every method, a transient transport
error only for an idempotent one; a server closing the connection
without responding is not retried. A Retry-After header is honored in
both the delta-seconds and the HTTP-date form with a
locale-independent parse, and is never shortened: the overall retry
budget bounds it while the per-sleep cap bounds only the backoff. Easy
handles are pooled, so a request reuses the previous connection
instead of paying a handshake every time; TLS below 1.2 is refused and
requests carry a default user agent. Every transported request is
debug-logged with the server request id.
- RestApi: HTTP + JSON protocol layer. "/v1/config" merges the server
options into the client ones (overrides > client options > defaults,
an override sent as null unsets the client option), "header." options
are sent as request headers and a listing follows the page tokens to
the last page. A failed request maps to a Status by the code of the
parsed error body, falling back to the http status, and carries a
RestErrorDetail with that code so callers can tell e.g. an
authentication failure from another IO error. A response body is never
echoed into an error, since it may carry credentials, so a body that
is not an error object is only reported as unparsable.
- RestCatalog: database, table and snapshot operations, with
"table-default." option defaults and a schema conversion computing the
highest field id (ids at or above the new
SpecialFieldIds::SYSTEM_FIELD_ID_START are excluded, duplicated ids
are rejected at any nesting level). A branch stays in the object name
so the server resolves it and returns the branch's own schema, the
default branch "main" being matched ignoring case and addressed as the
bare table. The "sys" database serves the local global system tables
like FileSystemCatalog.
- Bear token authentication provider ("bear" is the protocol's
historical spelling of "bearer"). RestAuthParameter::Create
url-encodes the query parameter values a signing provider signs.
- CatalogOptions: public catalog-level option keys (metastore, uri,
token, token.provider, "table-default." prefix), kept separate from
the table-level CoreOptions.
- CatalogUtils: system database, system table and branch checks shared
by FileSystemCatalog and RestCatalog.
- RestUtil::ExtractRequestId: the request id lookup shared by the debug
log line and the error message, falling back to any header whose name
carries a request id since a gateway may report it under its own name.
The pieces overlapping with the object store file systems move to
common/utils: UrlUtils carries both URL encoding flavors
(form-urlencoded for the REST api, RFC 3986 for S3), the generic HTTP
client moves there from common/fs, and the new SensitiveConfigUtils
redacts credentials for every surface that may expose them - the
sys.catalog_options table masks the value of a credential-carrying
option key, keeping at most a short tail of an identifier-like value,
and a server error message carrying a secret marker is redacted as a
whole.
libcurl is used from the system, so the new PAIMON_ENABLE_REST option
defaults to OFF like the other optional components with external
requirements; its CMake detection is a single find_package guarded by
PAIMON_ENABLE_S3 OR PAIMON_ENABLE_REST.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Purpose
Linked issue: #164
Add a REST catalog implementation, selected via the
metastore=restoption, that talks the Paimon REST catalog open API.New components
RestHttpClient— blocking libcurl client with exponential-backoff retries:Retry-Afterresponse header — delta-seconds or HTTP-date form, parsed locale-independently — takes precedence over the backoff when it yields a positive delay. Every retry sleep is bounded by a per-sleep cap and an overall retry budget; aRetry-Afterbeyond either bound stops retrying rather than sleeping less than the server requested.CURLOPT_POSTREDIR) and are restricted to http(s) targets.paimon-cppuser agent that aheader.User-Agentoption overrides.RestApi— HTTP + JSON layer:/v1/configoption merging (overrides > client options > defaults),header.options sent as request headers, and paged listing.Status(404 →NotExist, 409 →Exist, 400 →Invalid, 501 →NotImplemented) that prefers the code of the parsed error body over the http status.x-request-id, falling back to anyrequest-idheader) and attach aRestErrorDetailwith the mapped code, so callers can distinguish e.g. authentication failures programmatically.RestCatalog— database/table create/drop/rename/list/get, snapshot listing,table-default.option defaults on table creation, and system/branch table checks:TableSchemawith the computedhighestFieldId: ids at or aboveSpecialFieldIds::SYSTEM_FIELD_ID_STARTare excluded, a duplicated field id at any nesting level is rejected, and a missing or wrong-typedpartitionKeys/primaryKeys/optionsmember fails instead of silently defaulting.mainmaps case-insensitively to the bare table); snapshots of a branch are listed under the branch object name.sysdatabase serves the local global system tables likeFileSystemCatalog.token.provider=bear, the protocol's historical spelling of "bearer").Shared code
common/utils: the generic HTTP client moved there fromcommon/fsand is shared by the S3 file system and the REST catalog,UrlUtilscarries both URL-encoding flavors (form-urlencoded for the REST api, RFC 3986 for S3) with the S3 client reusing it, and libcurl detection in CMake is a singlefind_package(CURL)gated onPAIMON_ENABLE_S3 OR PAIMON_ENABLE_REST.CatalogUtilsholds the system-database and system/branch table checks shared byFileSystemCatalogandRestCatalog, with the check order and messages aligned with the JavaCatalogUtils(Cannot 'createTable' for system table ...).FileSystemCatalognow also rejects branch identifiers on create/drop/rename — a branch identifier resolves to the main table directory, so droppingt$branch_bwould otherwise delete the whole table.SensitiveConfigUtilscentralizes credential redaction with the JavaSensitiveConfigUtilskey markers and masking rule (matched on the key lower-cased with separators removed; a key naming a true secret is masked as a whole, while an identifier-like key such asdlf.access-key-idkeeps at most a four-character tail of a long enough value).sys.catalog_optionsmasks every row through it so credentials such as the bearer token never surface to users who can query that table, and a server error message carrying a secret marker is redacted as a whole.RestUtil::ExtractRequestIdandRestAuthParameter::Createlikewise hold the request-id lookup and the query-parameter encoding in one place, so the log line and the error message report the same id and no call site can sign an unencoded parameter.Build
libcurl is used from the system rather than bundled, so the new CMake option
PAIMON_ENABLE_RESTdefaults toOFF, consistent with the other optional components that need something outside the bundled third-party toolchain (jindo, lance, lumina, lucene, tantivy). CI enables it explicitly inci/scripts/build_paimon.shand installs the libcurl development package, so the REST code and its tests are still built and run on every job. With the option off,Catalog::Createreports thatmetastore=restrequires a build withPAIMON_ENABLE_REST=ON.Follow-ups (not in this PR): DLF signing, data-token file IO, paged listing parameters (
maxResults/name patterns), and the remaining endpoints (alter/commit/views/partitions/tags/functions).Tests
New
rest_testbinary (62 cases, registered under theunittestlabel so it runs in CI), all against an in-process mock HTTP/REST server:RestHttpClientTest: uri normalization, query encoding, a pooled handle carrying no method or body over from the previous request, retry classification (429/503 retried, 404 and non-allowlisted transport errors such as a refused connection or a redirect loop not, truncated responses retried only for idempotent methods),Retry-Afterprecedence in both forms plus the per-sleep and overall budget bounds, retry exhaustion, redirect following with POST keeping its method and body, rejection of invalid header names/values, and transport errors omitting the url and query.RestUtilTest/ResourcePathsTest/RestMessagesTest, each next to its implementation: prefix extraction and the request-id lookup including the gateway-header fallback, resource path building with url-encoded segments, JSON round trips of all request/response messages, and config merge with null-value filtering (a key present in defaults, client options and overrides at once, plus the null-override and null-default paths).RestCatalogTest: client-side option validation (missing uri/token, unsupported token provider), end-to-end catalog operations including pagination,ignore_if_exists/ignore_if_not_existspaths, client/server headers and the json content type,table-default.defaults, system tables, branch tables resolved by the server, broken-schema rejection (missing and wrong-typedpartitionKeys/primaryKeys/optionsmembers), a non-empty partition-key round trip, server errors surfacing as errors instead of "does not exist", and snapshot listing with sorting.RestApiErrorTest: http-status-to-Statusmapping including the request-id fallback, redaction and theRestErrorDetailcode.The shared pieces are covered next to their implementations:
UrlUtilsTest: both encoding flavors, including that a literal%is encoded even when/is preserved, which is what the S3 client relies on.SensitiveConfigUtilsTest: key classification, whole-value and tail-preserving masking, and free-form text redaction.HttpClientUtilTestin the common utils suite.TableSchemaTest.TestComputeHighestFieldId: the highest-field-id computation.SnapshotTest.TestToSnapshotInfo: the snapshot-to-info conversion.FileSystemCatalogTest.TestBranchIdentifierRejectedForTableOperations: the branch rejection.sys.catalog_optionsintegration test: credential-carrying options (token, access-key secret, account key, credential, SAS) are masked in every emitted row, and an access-key id keeps only its four-character tail.API and Format
include/paimon/catalog_options.hwith aPAIMON_EXPORTedCatalogOptionsstruct holding the catalog-level option keysMETASTORE,URI,TOKEN,TOKEN_PROVIDERand theTABLE_DEFAULT_OPTION_PREFIX(table-default.) key prefix; table-level keys stay inOptions(defs.h), mirroring the CatalogOptions/CoreOptions separation of the Java implementation.RestCatalogimplements theCataloginterface includingGetOptions(), which returns the client options merged with the server-side/v1/config.Catalog::Createnow dispatches on themetastoreoption (filesystemremains the default; unknown values are rejected). Formetastore=rest,root_pathis not a filesystem path but the warehouse (instance) name registered on the REST server, documented on the API.Documentation
docs/source/user_guide/catalog.rstnow documents both metastores: the existing filesystem metastore section and a new REST Catalog section covering thePAIMON_ENABLE_RESTbuild requirement, theroot_pathsemantics (warehouse/instance name), theCatalogOptionskeys (metastore,uri,token.provider,token,table-default.<key>) and a configuration example, replacing the note that claimed REST catalog support is future work. It also states which parts of the REST catalog are not implemented yet (altering a database or a table, views, functions, partitions, tags, branch management, consumers and thedlftoken provider), so the guide is not read as promising them.docs/source/building.rstlists-DPAIMON_ENABLE_REST=ONunder the optional components, including its libcurl requirement.Generative AI tooling
Generated-by: Claude Code (claude-fable-5, claude-opus-5)
🤖 Generated with Claude Code